package ga;

import java.io.IOException;

public class ConstructorArboles {
	
	public static final boolean DEBUG=false;
	private Token tact;
	private GA gramaticaAtributos = new GA();
	private AnalizadorLexico analizadorLexico;
	
	  public ConstructorArboles(AnalizadorLexico analizadorLexico) {
	        this.analizadorLexico = analizadorLexico;
	  }
	
	  public GA.Programa parse() throws IOException {
	        tact = analizadorLexico.sigToken();
	        GA.Programa aDeProg = recProg();
	        rec(CatLexica.EOF);
	        return aDeProg;
	    }
	
	private GA.Programa recProg() throws IOException{
		
		if (tokenActual(CatLexica.INT) || tokenActual(CatLexica.BOOLEAN )) {
			GA.Decs declaraciones = recDecs();
			rec(CatLexica.AMPERSAND);
			GA.Insts instrucciones = recInsts();
			return progR1(declaraciones,instrucciones);	
		}
		else{
			GA.Insts instrucciones = recInsts();
		
			return progR2(instrucciones);
		}
	}

	private GA.Decs recDecs() throws IOException{
		
		GA.Dec declaracion_a = recDec();
		GA.Decs rDeclaraciones = recRDecs(decsR2(declaracion_a));
		
		return  rDeclaraciones;
	}
	
	private GA.Decs recRDecs(GA.Decs declaracion_a) throws IOException{
		
		if (tokenActual(CatLexica.PYC)){
			rec(CatLexica.PYC);
			GA.Dec declaracion = recDec();
			GA.Decs rDeclaraciones = recRDecs(decsR1(declaracion_a, declaracion));
			
			return rDeclaraciones;
		}
		else
			return declaracion_a;
	}
	
	private GA.Dec recDec() throws IOException{
		
		GA.Tipo tipo = recTipo();
		Token tDeId = tact;
		rec(CatLexica.IDENT);
		
		return decR1(tipo,tDeId);
	}
	
	private GA.Tipo recTipo() throws IOException{
		
		if (tokenActual(CatLexica.BOOLEAN)){
			rec(CatLexica.BOOLEAN);
			return tipoR2();
		}
		else{
			rec(CatLexica.INT);
			return tipoR1();
		}
	}
	
	private GA.Insts recInsts() throws IOException{
		
		GA.Inst instruccion_a = recInst();
		GA.Insts rInstrucciones = recRInsts(instsR2(instruccion_a));
		
		return rInstrucciones;
	}
	
	private GA.Insts recRInsts(GA.Insts instruccion_a) throws IOException{
		
		if(tokenActual(CatLexica.PYC)){
			rec(CatLexica.PYC);
			GA.Inst instruccion = recInst();
			GA.Insts rInstrucciones = recRInsts(instsR1(instruccion_a, instruccion));
			
			return rInstrucciones;
		}
		else{
			return instruccion_a;
		}
	}
	
	private GA.Inst recInst() throws IOException{
		
		if(tokenActual(CatLexica.IDENT)){
			GA.IAsig iAsignacion = recIAsig();
			
			return instR1(iAsignacion);
			
		} else if(tokenActual(CatLexica.IF)){
			GA.IIF iif = recIIF();
			
			return instR2(iif);
			
		} else if(tokenActual(CatLexica.DO)){
			GA.IDO ido = recIDO();
			
			return instR3(ido);
		}
		else {
	          errorSintactico(CatLexica.IDENT, CatLexica.IF, CatLexica.DO);
	          return null;
	    }
	}
	
	private GA.IAsig recIAsig() throws IOException{
		
		Token tDeId = tact;
		rec(CatLexica.IDENT);
		rec(CatLexica.ASIG);
		GA.Exp0 exp0 = recExp0();
		
		return iAsigR1(exp0,tDeId);
	}
	
	private GA.IIF recIIF() throws IOException{
		
		rec(CatLexica.IF);
		GA.Casos casos = recCasos();
		rec(CatLexica.FI);
		
		return iifR1(casos);
		
	}
	
	private GA.IDO recIDO() throws IOException{
		
		rec(CatLexica.DO);
		GA.Casos casos = recCasos();
		rec(CatLexica.OD);
		
		return idoR1(casos);
	}
	
	private GA.Casos recCasos() throws IOException{
		
		GA.Caso caso = recCaso();
		GA.Casos rCasos = recRCasos(casosR2(caso));
		
		return rCasos;
	}
	
	private GA.Casos recRCasos(GA.Casos caso_a) throws IOException{
		
		if(tokenActual(CatLexica.CORCHETES)){
			
			rec(CatLexica.CORCHETES);
			GA.Caso caso = recCaso();
			GA.Casos rCasos = recRCasos(casosR1(caso_a, caso));
			
			return rCasos;
		}
		else{
			return caso_a;
		}
	}
	
	private GA.Caso recCaso() throws IOException{
		
		rec(CatLexica.CASE);
		GA.Exp0 exp0 = recExp0();
		rec(CatLexica.FLECHA);
		GA.Insts instrucciones = recInsts();
		
		return casoR1(exp0, instrucciones);
	}
	
	private GA.Exp0 recExp0() throws IOException{
		
		GA.Exp1 exp1 = recExp1();
		GA.Exp0 rExp0 = recRExp0(exp1);
		
		return rExp0;
	}
	
	private GA.Exp0 recRExp0(GA.Exp1 exp1_a) throws IOException{
		
		if(tokenActual(CatLexica.EQ) || tokenActual(CatLexica.NEQ) || tokenActual(CatLexica.GT) ||
				tokenActual(CatLexica.GE) || tokenActual(CatLexica.LT) || tokenActual(CatLexica.LE))
		{
			GA.OpComp opComparacion = recOpComp();
			GA.Exp1 exp1 = recExp1();
			
			return exp0R1(exp1_a, opComparacion, exp1);
		}
		else
		{
			return exp0R2(exp1_a);
		}
	}
	
	private GA.Exp1 recExp1() throws IOException{
		
		GA.Exp2 exp2_a = recExp2();
		GA.Exp1 rExp1 = recRExp1(exp1R2(exp2_a));
		
		return rExp1;
	}
	
	private GA.Exp1 recRExp1(GA.Exp1 exp2_a) throws IOException{
		
		if (tokenActual(CatLexica.SUMA) || tokenActual(CatLexica.RESTA) || tokenActual(CatLexica.OR))
		{
			GA.OpAdi opAditivo = recOpAdi();
			GA.Exp2 exp2 = recExp2();
			GA.Exp1 rExp1 = recRExp1(exp1R1(exp2_a,opAditivo, exp2));
			
			return rExp1;
			
		}
		else 
			return exp2_a;		
	}
	
	private GA.Exp2 recExp2() throws IOException{
		
		GA.Exp3 exp3_a = recExp3();
		GA.Exp2 rExp2 = recRExp2(exp2R2(exp3_a));
		
		return rExp2;
	}
	
	private GA.Exp2 recRExp2(GA.Exp2 exp3_a) throws IOException{
		
		if (tokenActual(CatLexica.MULT) || tokenActual(CatLexica.DIV) || tokenActual(CatLexica.AND))
		{
			GA.OpMult opMul = recOpMult();
			GA.Exp3 exp3 = recExp3();
			GA.Exp2 rExp2 = recRExp2(exp2R1(exp3_a,opMul,exp3));
			
			return rExp2;
		}
		else 
			return exp3_a;
	}
	
	private GA.OpComp recOpComp () throws IOException{
		
		if (tokenActual(CatLexica.EQ)){
			rec(CatLexica.EQ);
			
			return compR1();
		}
		else if (tokenActual(CatLexica.NEQ)){
			rec(CatLexica.NEQ);
			
			return compR2();
		}
		else if (tokenActual(CatLexica.GT)){
			rec(CatLexica.GT);
			
			return compR3();
		}
		else if (tokenActual(CatLexica.GE)){
			rec(CatLexica.GE);
			
			return compR4();
		}
		else if (tokenActual(CatLexica.LT)){
			rec(CatLexica.LT);
			
			return compR5();
		}
		else if (tokenActual(CatLexica.LE)){
			rec(CatLexica.LE);
			
			return compR6();
		}
		else {
	          errorSintactico(CatLexica.EQ, CatLexica.NEQ, CatLexica.GT, CatLexica.GE, CatLexica.LT, CatLexica.LE);
	          return null;
	    } 
	}
	private GA.Exp3 recExp3() throws IOException{
		if(tokenActual(CatLexica.RESTA) || tokenActual(CatLexica.NOT))
		{
			GA.OpUna opuna_a = recOpUna();	
			GA.Exp3 exp3_a = recExp3();
			return expr3R1(opuna_a,exp3_a);
		}		
		else
		{
			GA.Exp4 exp4_a = recExp4();
			return expr3R2(exp4_a);
		}
	}
	
	private GA.Exp4 recExp4() throws IOException{
		if(tokenActual(CatLexica.TRUE))
		{
			Token tTrue=tact;
			rec(CatLexica.TRUE);
			return expr4R1(tTrue);
		}	
		else if(tokenActual(CatLexica.FALSE))
		{
			Token tFalse=tact;
			rec(CatLexica.FALSE);
			return expr4R2(tFalse);
		}	
		else if(tokenActual(CatLexica.NUM))
		{
			Token tNum=tact;
			rec(CatLexica.NUM);
			return expr4R3(tNum);
		}	
		else if(tokenActual(CatLexica.IDENT))
		{
			Token tIden=tact;
			rec(CatLexica.IDENT);
			return expr4R4(tIden);
		}	
		else if(tokenActual(CatLexica.APARENTESIS))
		{
			rec(CatLexica.APARENTESIS);
	        GA.Exp0 exp0_a = recExp0();
	        rec(CatLexica.CPARENTESIS);
			return expr4R5(exp0_a);
		}
		else {
	          errorSintactico(CatLexica.TRUE, CatLexica.FALSE, CatLexica.NUM, CatLexica.IDENT, CatLexica.APARENTESIS);
	          return null;
	    } 
	}
	
	
	private GA.OpAdi recOpAdi() throws IOException{
		
		if(tokenActual(CatLexica.SUMA)){
			rec(CatLexica.SUMA);
			
			return adiR1();
		}
		else if(tokenActual(CatLexica.RESTA)){
			rec(CatLexica.RESTA);
			
			return adiR2(); 
		}
		else if(tokenActual(CatLexica.OR)){
			rec(CatLexica.OR);
			
			return adiR3();
		}
		else {
	          errorSintactico(CatLexica.SUMA, CatLexica.RESTA, CatLexica.OR);
	          return null;
	    } 
	}
	
	private GA.OpMult recOpMult() throws IOException{
		
		if(tokenActual(CatLexica.MULT)){
			rec(CatLexica.MULT);
			
			return multR1();
		}
		else if(tokenActual(CatLexica.DIV)){
			rec(CatLexica.DIV);
			
			return multR2(); 
		}
		else if(tokenActual(CatLexica.AND)){
			rec(CatLexica.AND);
			
			return multR3();
		}
		else {
	          errorSintactico(CatLexica.MULT, CatLexica.DIV, CatLexica.AND);
	          return null;
	    }
	}
	
	private GA.OpUna recOpUna() throws IOException{
		
		if(tokenActual(CatLexica.RESTA)){
			rec(CatLexica.RESTA);
			
			return unaR1();
		}
		else if(tokenActual(CatLexica.NOT)){
			rec(CatLexica.NOT);
			
			return unaR2(); 
		}	
		else {
	          errorSintactico(CatLexica.RESTA, CatLexica.NOT);
	          return null;
	    }
	}
	
	private void rec(CatLexica cat) throws IOException {
        if (tokenActual(cat)) {
            tact = sigToken();
        } else {
            errorSintactico(cat);
        }
    }

    private void errorSintactico(CatLexica... catsEsperadas) {
        System.err.print("(" + tact.getFila() + "," + tact.getCol() + ")" 
                +"ERROR SINTACTICO: Encontrado " + tact + 
                ". Se esperaba alguno de los siguientes elementos: ");
        for (CatLexica catEsperada : catsEsperadas) {
            System.err.print(catEsperada + " ");
        }
        System.err.println();
        System.exit(1);
    }
    
    private boolean tokenActual(CatLexica ... cats) {
        boolean encontrada = false;
        int i=0;
        while (i < cats.length && ! encontrada) {
           encontrada = tact.getCategoria() == cats[i];
           i++;
        }
        return encontrada;
    }
    
    private Token sigToken() throws IOException {
       return analizadorLexico.sigToken();
    }
    
    private GA.Programa progR1(GA.Decs decs, GA.Insts insts){
    	if (DEBUG) return gramaticaAtributos.new ProgR1Debug(decs,insts);
    	else return gramaticaAtributos.new ProgR1(decs,insts);
    }

    private GA.Programa progR2(GA.Insts insts){
    	if (DEBUG) return gramaticaAtributos.new ProgR2Debug(insts);
    	else return gramaticaAtributos.new ProgR2(insts);
    }
    
    private GA.Decs decsR1(GA.Decs dec_a, GA.Dec dec){
    	if (DEBUG) return gramaticaAtributos.new decsR1Debug(dec_a,dec);
    	else return gramaticaAtributos.new decsR1(dec_a,dec);
    }
    
    private GA.Decs decsR2(GA.Dec dec_a){
    	if (DEBUG) return gramaticaAtributos.new decsR2Debug(dec_a);
    	else return gramaticaAtributos.new decsR2(dec_a);
    }
    
    private GA.Dec decR1(GA.Tipo tipo, Token to){
    	if (DEBUG) return gramaticaAtributos.new decR1Debug(tipo,to);
    	else return gramaticaAtributos.new decR1(tipo,to);
    }
    
    private GA.Tipo tipoR1(){
    	if (DEBUG) return gramaticaAtributos.new tipoR1Debug();
    	else return gramaticaAtributos.new tipoR1();
    }
    
    private GA.Tipo tipoR2(){
    	if (DEBUG) return gramaticaAtributos.new tipoR2Debug();
    	else return gramaticaAtributos.new tipoR2();
    }
    
    private GA.Insts instsR1(GA.Insts insts, GA.Inst inst){
    	if (DEBUG) return gramaticaAtributos.new instsR1Debug(insts,inst);
    	else return gramaticaAtributos.new instsR1(insts,inst);
    }
    
    private GA.Insts instsR2(GA.Inst inst){
    	if (DEBUG) return gramaticaAtributos.new instsR2Debug(inst);
    	else return gramaticaAtributos.new instsR2(inst);
    }
    
    private GA.Inst instR1(GA.IAsig asig){
    	if (DEBUG) return gramaticaAtributos.new instR1Debug(asig);
    	else return gramaticaAtributos.new instR1(asig);
    }
    
    private GA.Inst instR2(GA.IIF iif){
    	if (DEBUG) return gramaticaAtributos.new instR2Debug(iif);
    	else return gramaticaAtributos.new instR2(iif);
    }
    
    private GA.Inst instR3(GA.IDO ido){
    	if (DEBUG) return gramaticaAtributos.new instR3Debug(ido);
    	else return gramaticaAtributos.new instR3(ido);
    }
    
    private GA.IAsig iAsigR1(GA.Exp0 exp0, Token to){
    	if (DEBUG) return gramaticaAtributos.new iAsigR1Debug(exp0,to);
    	else return gramaticaAtributos.new iAsigR1(exp0,to);
    }
    
    private GA.IIF iifR1(GA.Casos casos){
    	if (DEBUG) return gramaticaAtributos.new iifR1Debug(casos);
    	else return gramaticaAtributos.new iifR1(casos);
    }
    
    private GA.IDO idoR1(GA.Casos casos){
    	if (DEBUG) return gramaticaAtributos.new idoR1Debug(casos);
    	else return gramaticaAtributos.new idoR1(casos);
    }
    
    private GA.Casos casosR1(GA.Casos casos, GA.Caso caso){
    	if (DEBUG) return gramaticaAtributos.new casosR1Debug(casos,caso);
    	else return gramaticaAtributos.new casosR1(casos,caso);
    }
    
    private GA.Casos casosR2(GA.Caso caso){
    	if (DEBUG) return gramaticaAtributos.new casosR2Debug(caso);
    	else return gramaticaAtributos.new casosR2(caso);
    }
    
    private GA.Caso casoR1(GA.Exp0 exp0, GA.Insts insts){
    	if (DEBUG) return gramaticaAtributos.new casoR1Debug(exp0,insts);
    	else return gramaticaAtributos.new casoR1(exp0,insts);
    }
    
    private GA.Exp0 exp0R1(GA.Exp1 exp1_a, GA.OpComp opComparacion, GA.Exp1 exp1){
    	if (DEBUG) return gramaticaAtributos.new exp0R1Debug(exp1_a,opComparacion,exp1);
    	else return gramaticaAtributos.new exp0R1(exp1_a,opComparacion,exp1);
    }
    
    private GA.Exp0 exp0R2(GA.Exp1 exp1){
    	if (DEBUG) return gramaticaAtributos.new exp0R2Debug(exp1);
    	else return gramaticaAtributos.new exp0R2(exp1);
    }
    
    private GA.Exp1 exp1R1(GA.Exp1 exp2_a, GA.OpAdi opAdi, GA.Exp2 exp2){
    	if (DEBUG) return gramaticaAtributos.new exp1R1Debug(exp2_a,opAdi,exp2);
    	else return gramaticaAtributos.new exp1R1(exp2_a,opAdi,exp2);
    }
    
    private GA.Exp1 exp1R2(GA.Exp2 exp2){
    	if (DEBUG) return gramaticaAtributos.new exp1R2Debug(exp2);
    	else return gramaticaAtributos.new exp1R2(exp2);
    }  
    
    private GA.Exp2 exp2R1(GA.Exp2 exp3_a, GA.OpMult opMult, GA.Exp3 exp3){
    	if (DEBUG) return gramaticaAtributos.new exp2R1Debug(exp3_a,opMult,exp3);
    	else return gramaticaAtributos.new exp2R1(exp3_a,opMult,exp3);
    }
    
    private GA.Exp2 exp2R2(GA.Exp3 exp3){
    	if (DEBUG) return gramaticaAtributos.new exp2R2Debug(exp3);
    	else return gramaticaAtributos.new exp2R2(exp3);
    }
    
    private GA.OpComp compR1(){
    	if (DEBUG) return gramaticaAtributos.new compR1();
    	else return gramaticaAtributos.new compR1();
    }
    
    private GA.OpComp compR2(){
    	if (DEBUG) return gramaticaAtributos.new compR2();
    	else return gramaticaAtributos.new compR2();
    }
    
    private GA.OpComp compR3(){
    	if (DEBUG) return gramaticaAtributos.new compR3();
    	else return gramaticaAtributos.new compR3();
    }
    
    private GA.OpComp compR4(){
    	if (DEBUG) return gramaticaAtributos.new compR4();
    	else return gramaticaAtributos.new compR4();
    }
    
    private GA.OpComp compR5(){
    	if (DEBUG) return gramaticaAtributos.new compR5();
    	else return gramaticaAtributos.new compR5();
    }
    
    private GA.OpComp compR6(){
    	if (DEBUG) return gramaticaAtributos.new compR6();
    	else return gramaticaAtributos.new compR6();
    }
    
    private GA.Exp3 expr3R1(GA.OpUna opuna_a, GA.Exp3 exp3_a){
    	if (DEBUG) return gramaticaAtributos.new exp3R1(opuna_a,exp3_a);
    	else return gramaticaAtributos.new exp3R1(opuna_a,exp3_a);
    }
    
    private GA.Exp3 expr3R2(GA.Exp4 exp4_a){
    	if (DEBUG) return gramaticaAtributos.new exp3R2(exp4_a);
    	else return gramaticaAtributos.new exp3R2(exp4_a);
    }
    
    private GA.Exp4 expr4R1(Token to){
    	if (DEBUG) return gramaticaAtributos.new exp4R1(to);
    	else return gramaticaAtributos.new exp4R1(to);
    }
    
    private GA.Exp4 expr4R2(Token to){
    	if (DEBUG) return gramaticaAtributos.new exp4R2(to);
    	else return gramaticaAtributos.new exp4R2(to);
    }
    
    private GA.Exp4 expr4R3(Token to){
    	if (DEBUG) return gramaticaAtributos.new exp4R3(to);
    	else return gramaticaAtributos.new exp4R3(to);
    }
    
    private GA.Exp4 expr4R4(Token to){
    	if (DEBUG) return gramaticaAtributos.new exp4R4(to);
    	else return gramaticaAtributos.new exp4R4(to);
    }
    
    private GA.Exp4 expr4R5(GA.Exp0 exp0){
    	if (DEBUG) return gramaticaAtributos.new exp4R5(exp0);
    	else return gramaticaAtributos.new exp4R5(exp0);
    }
    
    private GA.OpAdi adiR1(){
    	if (DEBUG) return gramaticaAtributos.new adiR1();
    	else return gramaticaAtributos.new adiR1();
    }
    
    private GA.OpAdi adiR2(){
    	if (DEBUG) return gramaticaAtributos.new adiR2();
    	else return gramaticaAtributos.new adiR2();
    }
    
    private GA.OpAdi adiR3(){
    	if (DEBUG) return gramaticaAtributos.new adiR3();
    	else return gramaticaAtributos.new adiR3();
    }
    
    private GA.OpMult multR1(){
    	if (DEBUG) return gramaticaAtributos.new multR1();
    	else return gramaticaAtributos.new multR1();
    } 
    
    private GA.OpMult multR2(){
    	if (DEBUG) return gramaticaAtributos.new multR2();
    	else return gramaticaAtributos.new multR2();
    } 
    
    private GA.OpMult multR3(){
    	if (DEBUG) return gramaticaAtributos.new multR3();
    	else return gramaticaAtributos.new multR3();
    } 
    
    private GA.OpUna unaR1(){
    	if (DEBUG) return gramaticaAtributos.new unaR1();
    	else return gramaticaAtributos.new unaR1();
    } 
    
    private GA.OpUna unaR2(){
    	if (DEBUG) return gramaticaAtributos.new unaR2();
    	else return gramaticaAtributos.new unaR2();
    } 
}